/**
 * @file hs_logger.h
 * @author iam002
 * @brief 日志类及其宏接口定义
 * @version 1.0
 * @date 2024-10-14
 * 
 * @copyright Copyright (c) 2024
 * 
 */
#ifndef __HS_LOGGER_H__
#define __HS_LOGGER_H__

#include <iostream>
#include <string>
#include <sstream>
#include <iomanip>
#include <fstream>
#include "hs_version.h"

#ifdef USE_MEX
#include <mex.hpp>
/**
 * @brief MexFunction 初始化宏, 建议放在 MexFunction 类定义的首行
 * 
 */
#define HS_LOGGER_INIT_MEXFUNCTION \
public: \
    MexFunction() \
    { \
        m_matlab = getEngine(); \
        _global_logger.setEngine(m_matlab); \
    } \
private: \
    std::shared_ptr<matlab::engine::MATLABEngine> m_matlab;
#endif

namespace hs
{

/**
 * @brief 日志等级
 * 
 */
enum class LogLevelType
{
    DEBUG,      // DEBUG模式下有效
    INFO,       // 用于一般输出，可手动关闭
    WARN,       // 显示警告信息，程序不终止
    ERROR,      // 显示错误信息，程序不终止
    FATAL,      // 显示错误信息，终止程序
    NONE,
};

/**
 * @brief 简易日志类
 * 
 * @note 
 *  1. 此日志类不能保证线程安全, 禁止在多线程代码块中调用此类的方法。
 *  2. 此日志类未做任何优化和安全措施, 对日志性能和可靠性有要求请使用
 *     专用的日志库如"easylogging++"等。
 */
class Logger
{

public:
    /**
     * @brief 构造函数
     * 
     */
    Logger();

    /**
     * @brief 析构函数
     * 
     */
    ~Logger();

    /**
     * @brief 日志输出接口
     * 
     * @param level 日志级别
     * @param os std::ostringstream对象
     */
    void send(LogLevelType level, const std::ostringstream & os);

    /**
     * @brief 日志输出接口
     * 
     * @param level 日志级别
     * @param str 字符串
     */
    void send(LogLevelType level, const std::string & str);

    /**
     * @brief 输出日志(无任何前缀)
     * 
     * @param str 
     */
    void send(const std::string & str);
    
    /**
     * @brief 设置日志文件的输出路径并打开文件流
     * 
     * @param log_path 日志输出路径，用户需要保证路径合法
     * @param enableAppend 是否已追加的形式打开, 默认为 false
     */
    void open(const std::string & log_path, bool enableAppend = false);

    /**
     * @brief 关闭日志文件
     * 
     */
    void close();

    /**
     * @brief 设置浮点数打印精度
     * 
     * @param num 
     */
    void setprecision(int num);

#ifdef USE_MEX
    void setEngine(std::shared_ptr<matlab::engine::MATLABEngine> matlab)
    {
        m_matlab = matlab;
    }
#endif

private:
    /* 各级日志输出接口 */
    void debug(const std::string & msg);
    void info(const std::string & msg);
    void warn(const std::string & msg);
    void error(const std::string & msg);
    void fatal(const std::string & msg);

    /* 日志级别前缀 */
    const char* levelPrefix(LogLevelType level);
   
    /* 时间前缀 */
    void timePrefix(std::string & time_str);

public:
    /// @brief 是否启用INFO日志输出
    bool enableInfo;

    /// @brief 是否启用日志前缀
    bool enableLevelPrefix;
    
    /// @brief 是否启用时间前缀
    bool enableTimePrefix;

    /// @brief 是否启用简易的日志级别前缀
    bool enableSimpleLevelPrefix;

    /// @brief 是否关闭终端日志显示
    bool enableOnlyToFile;

    /// @brief 浮点数打印精度
    int floatPrecisionNum;

private:
    /// @brief 日志文件
    std::ofstream m_log_file;

#ifdef USE_MEX
    /// @brief matlab引擎
    std::shared_ptr<matlab::engine::MATLABEngine> m_matlab;
#endif

}; // end class Logger

} // end namespace hs

/**
 * @brief 日志宏接口
 * 
 * @example 
 *  - 方式 1: 单行输出模式
 *      LOG_INFO("THIS IS" $ 1 $ std::endl);
 *      LOG_INFO("THIS IS" << 1 << ", NEXT\n");
 *  - 方式 2: 多行输出模式
 *      LOG_BUFFER_BEGIN
 *      LOG_BUFFER("test 1" $ " test 1\n");     
 *      LOG_BUFFER("test 2" $ " test 2\n");     
 *      LOG_BUFFER_END
 */
#define $ << 

#ifndef NDEBUG
#define LOG_DEBUG(A) do { \
    std::ostringstream os; \
    if (_global_logger.floatPrecisionNum) { \
        os << std::fixed << std::setprecision(_global_logger.floatPrecisionNum); \
    } \
    os << A; \
    _global_logger.send(hs::LogLevelType::DEBUG, os); \
}while(0)
#else
#define LOG_DEBUG(A) do { } while(0)
#endif

#define LOG_INFO(A) do { \
    std::ostringstream os; \
    if (_global_logger.floatPrecisionNum) { \
        os << std::fixed << std::setprecision(_global_logger.floatPrecisionNum); \
    } \
    os << A; \
    _global_logger.send(hs::LogLevelType::INFO, os); \
}while(0)

#define LOG_WARN(A) do { \
    std::ostringstream os; \
    if (_global_logger.floatPrecisionNum) { \
        os << std::fixed << std::setprecision(_global_logger.floatPrecisionNum); \
    } \
    os << A; \
    _global_logger.send(hs::LogLevelType::WARN, os); \
}while(0)

#define LOG_ERROR(A) do { \
    std::ostringstream os; \
    if (_global_logger.floatPrecisionNum) { \
        os << std::fixed << std::setprecision(_global_logger.floatPrecisionNum); \
    } \
    os << A; \
    _global_logger.send(hs::LogLevelType::ERROR, os); \
}while(0)

#define LOG_FATAL(A) do { \
    std::ostringstream os; \
    if (_global_logger.floatPrecisionNum) { \
        os << std::fixed << std::setprecision(_global_logger.floatPrecisionNum); \
    } \
    os << A; \
    _global_logger.send(hs::LogLevelType::FATAL, os); \
}while(0)

#define LOG_SEND(A) do { \
    std::ostringstream os; \
    if (_global_logger.floatPrecisionNum) { \
        os << std::fixed << std::setprecision(_global_logger.floatPrecisionNum); \
    } \
    os << A; \
    _global_logger.send(os.str()); \
}while(0)

/* 多行日志输出 */
#define LOG_BUFFER_BEGIN { std::ostringstream os;
#define LOG_BUFFER(A) do { \
    if (_global_logger.floatPrecisionNum) { \
        os << std::fixed << std::setprecision(_global_logger.floatPrecisionNum); \
    } \
    os << A; } while(0)
#define LOG_BUFFER_END_DEBUG _global_logger.send(hs::LogLevelType::DEBUG, os ); }
#define LOG_BUFFER_END _global_logger.send(hs::LogLevelType::INFO, os); }
#define LOG_BUFFER_END_WARN _global_logger.send(hs::LogLevelType::WARN, os); }
#define LOG_BUFFER_END_ERROR _global_logger.send(hs::LogLevelType::ERROR, os); }
#define LOG_BUFFER_END_FATAL _global_logger.send(hs::LogLevelType::FATAL, os); }

/* 是否启用INFO */
#define HS_LOG_ENABLE_INFO(X) _global_logger.enableInfo = (X)

/* 是否启用日志级别前缀 */
#define HS_LOG_ENABLE_LEVEL_PREFIX(X) _global_logger.enableLevelPrefix = (X)

/* 是否启用简易日志级别前缀 */
#define HS_LOG_ENABLE_SIMPLE_LEVEL_PREFIX(X) _global_logger.enableSimpleLevelPrefix = (X)

/* 是否启用时间戳前缀 */
#define HS_LOG_ENABLE_TIME_PREFIX(X) _global_logger.enableTimePrefix = (X)

/* 打开日志文件 */
#define HS_LOG_OPEN_FILE(X) _global_logger.open(X, false)
#define HS_LOG_APPEND_FILE(X) _global_logger.open(X, true)

/* 关闭日志文件 */
#define HS_LOG_CLOSE_FILE _global_logger.close()

/* 是否启用只输出到文件 */
#define HS_LOG_ENABLE_ONLY_TO_FILE(X) _global_logger.enableOnlyToFile = (X)

/* 设置浮点数输出精度 */
#define HS_LOG_SET_PRECISION(X) _global_logger.setprecision(X)

/* 获取日志属性值 */
#define HS_LOG_GET_PROPERTY(X) (_global_logger.X)

/**
 * @brief 全局变量
 * 
 */
extern hs::Logger _global_logger;

#endif
